package database.list;

import database.inter.IDB;
import database.singleDB.SingleDB;
import datastruct.list.impl.JoList;
import datastruct.list.impl.JoListII;
import datastruct.list.inter.IExpected;
import datastruct.list.inter.IJoList;
import datastruct.list.inter.IListConsumer;
import jodis.conn.impl.*;
import jodis.conn.inter.IReply;
import rdb.RDBConstant;
import tcp.Server;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class JList {

    public static IJoList getAsList(IDB idb, String key) {
        if (idb instanceof SingleDB) {
            SingleDB db = (SingleDB) idb;
            Object obj = db.getEntity(key);
            return (IJoList) obj;
        }
        return null;
    }

    public static IJoList getOrInitList(IDB idb, String key) {
        IJoList retList = getAsList(idb, key);
        if (retList == null) {
            retList = JoListII.makeListII();
            SingleDB db = (SingleDB) idb;
            db.putEntity(key, retList);
        }
        return retList;
    }

    public static IReply execRPush(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        IJoList retList = getOrInitList(idb, key);

        for (int i=2;i<cmdLine.length;i++) {
            retList.add(cmdLine[i]);
        }

        SingleDB db = (SingleDB) idb;
        db.putType(key, RDBConstant.TYPE_LIST);

        if (Server.getServerConfig().appendOnly) {
            db.iAddAof.addAof(db.index, cmdLine);
        }
        return IntReply.makeIntReply(retList.Len());
    }

    // LPUSH key element [element ...]
    public static IReply execLPush(IDB idb, String[] cmdLines) {
        String key = cmdLines[1];
        IJoList retList = getOrInitList(idb, key);

        for (int i=2;i<cmdLines.length;i++) {
            retList.insert(0, cmdLines[i], true);
        }

        SingleDB db = (SingleDB) idb;
        db.putType(key, RDBConstant.TYPE_LIST);

        if (Server.getServerConfig().appendOnly) {
            db.iAddAof.addAof(db.index, cmdLines);
        }
        return IntReply.makeIntReply(retList.Len());
    }

    public static String[][] undoLPush(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        int count = cmdLine.length-2;
        String[][] ret = new String[count][2];
        for (int i=0;i<count;i++) {
            ret[i][0] = "LPop";
            ret[i][1] = key;
        }
        return ret;
    }

    public static String[][] undoRPush(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        int count = cmdLine.length-2;
        String[][] ret = new String[count][2];
        for (int i=0;i<count;i++) {
            ret[i][0] = "RPop";
            ret[i][1] = key;
        }
        return ret;
    }

//    RPOP key [count]
    public static IReply execRPop(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        IJoList retList = getAsList(idb, key);
        if (retList == null) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }

        int count = cmdLine.length > 2 ? Integer.parseInt(cmdLine[2]) : 1;
        ArrayList<String> removeList = new ArrayList<>();
        for (int i=0;i<count&&retList.Len()>0;i++) {
            Object obj = retList.RemoveLast();
            if (obj != null) {
                String s = (String) obj;
                removeList.add(s);
            }
        }
        SingleDB db = (SingleDB) idb;

        if (retList.Len() == 0) {
            db.removeEntity(key);
        }

        String[] res = new String[removeList.size()];
        int i = 0;
        for (String s : removeList) {
            res[i] = s;
            i++;
        }


        if (Server.getServerConfig().appendOnly) {
            db.iAddAof.addAof(db.index, cmdLine);
        }
        return MultiBulkReply.makeMultiBulkReply(res);
    }

    //  LPop key [count]
    public static IReply execLPop(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        IJoList retList = getAsList(idb, key);
        if (retList == null) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }

        int count = cmdLine.length > 2 ? Integer.parseInt(cmdLine[2]) : 1;
        ArrayList<String> removeList = new ArrayList<>();
        for (int i=0;i<count&&retList.Len()>0;i++) {
            Object obj = retList.RemoveFirst();
            if (obj != null) {
                String s = (String) obj;
                removeList.add(s);
            }
        }

        SingleDB db = (SingleDB) idb;

        if (retList.Len() == 0) {
            db.removeEntity(key);
        }

        String[] res = new String[removeList.size()];
        int i = 0;
        for (String s : removeList) {
            res[i] = s;
            i++;
        }


        if (Server.getServerConfig().appendOnly) {
            db.iAddAof.addAof(db.index, cmdLine);
        }
        return MultiBulkReply.makeMultiBulkReply(res);
    }

    // LPOP key [count]
    public static String[][] undoLPop(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        IJoList retList = getAsList(idb, key);

        // 在命令执行之前，这个 key 就不存在，所以在回滚时，也要还原成不存在
        if (retList == null) {
            return new String[][] {
                    {"Del", cmdLine[1]}
            };
        }

        if (retList.Len() == 0) {
            return new String[][]{};
        }

        int count = 1;
        if (cmdLine.length == 3) {
            count = Integer.parseInt(cmdLine[2]);
        }

        String[] res = retList.range(0, count);
        String[] items = new String[2+res.length];
        items[0] = "LPush";
        items[1] = key;

        for (int i = 0;i<res.length;i++) {
            items[2+i] = res[i];
        }

        return new String[][]{items};
    }

    // RPOP key [count]
    public static String[][] undoRPop(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        IJoList retList = getAsList(idb, key);

        // 在命令执行之前，这个 key 就不存在，所以在回滚时，也要还原成不存在
        if (retList == null) {
            return new String[][] {
                    {"Del", cmdLine[1]}
            };
        }

        if (retList.Len() == 0) {
            return new String[][]{};
        }

        int count = 1;
        if (cmdLine.length == 3) {
            count = Integer.parseInt(cmdLine[2]);
        }

        int size = retList.Len();
        String[] res = retList.range(size-count, size);
        String[] items = new String[2+res.length];
        items[0] = "RPush";
        items[1] = key;

        for (int i = 0;i<res.length;i++) {
            items[2+i] = res[i];
        }

        return new String[][]{items};
    }

//    LRange key start stop  [start, stop]
    public static IReply execLRange(IDB idb, String[] cmdLine) {
        if (cmdLine.length < 4) {
            return StandardErrReply.makeErrReply("illegal num of arguments");
        }
        String key = cmdLine[1];
        int start = Integer.parseInt(cmdLine[2]);
        int stop = Integer.parseInt(cmdLine[3]);

        IJoList retList = getAsList(idb, key);
        if (retList == null) {
            System.out.println("retList is null");
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }
        int size = retList.Len();
        if (start < -1 * size) {
            start = 0;
        } else if (start < 0) {
            start = size+start;
        } else if (start >= size) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }

        if (stop < -1 * size) {
            stop = 0;
        } else if (stop < 0) {
            stop = size + stop + 1;
        } else if (stop < size) {
            stop++;
        } else {
            stop = size;
        }

        if (stop < start) {
            stop = start;
        }

        // 上面已经给 stop + 1，所以这里是 [start, stop)
        Object[] objects = retList.range(start, stop);

        String[] res = new String[objects.length];
        for (int i=0;i<res.length;i++) {
            res[i] = (String) objects[i];
        }
        return MultiBulkReply.makeMultiBulkReply(res);
    }

    //LINDEX key index
    // 这里 index 可以是负数，表示从结尾开始数，-1 表示倒数第一个，-2 表示倒数第二个
    public static IReply execLIndex(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        try {
            int index = Integer.parseInt(cmdLines[2]);
            if (index < -retList.Len() || index >= retList.Len()) {
                return NullReply.makeNullReply();
            } else if (index < 0) {
                index += retList.Len();
            }
            String res = (String) retList.get(index);
            return MultiBulkReply.makeMultiBulkReply(new String[]{res});
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    public static IReply execLLen(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        return retList == null ? IntReply.makeIntReply(0) : IntReply.makeIntReply(retList.Len());
    }

//    LINSERT key <BEFORE | AFTER> pivot element
    public static IReply execLInsert(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        if (retList == null || retList.Len() == 0) {
            return IntReply.makeIntReply(0);
        }
        boolean isBefore = cmdLines[2].equalsIgnoreCase("before");
        int pivotIndex = retList.indexOfVal(cmdLines[3]);
        // pivot 不存在，返回 -1
        if (pivotIndex == -1) {
            return IntReply.makeIntReply(-1);
        }

        retList.insert(pivotIndex, cmdLines[4], isBefore);
        return IntReply.makeIntReply(retList.Len());
    }
    //    LINSERT key <BEFORE | AFTER> pivot element
    public static String[][] undoLInsert(IDB idb, String[] cmdLines) {
        IJoList list = getAsList(idb, cmdLines[1]);
        // 如果命令执行前，key 对应的 list 就不存在，那么回滚时要保证 key 不存在
        if (list == null) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        // 如果 key 对应的 list 存在，删除插入的元素
        String[] items = {"LRem", cmdLines[1], "1", cmdLines[4]};
        return new String[][]{items};
    }

//    LREM key count element
    /*
    count > 0 移除前 count 个 element
    count < 0 移除后 count 个 element
    count = 0 移除所有 element
     */
    public static IReply execLRem(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        if (retList == null || retList.Len() == 0) {
            return IntReply.makeIntReply(0);
        }

        try {
            int count = Integer.parseInt(cmdLines[2]);
            int res = retList.RemoveFrom(count, Math.abs(count), cmdLines[3]);
            return IntReply.makeIntReply(res);
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    //    LREM key count element
    public static String[][] undoLRem(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        // 在命令执行前，key 对应的 list 就不存在，那么回滚时仍然保持不存在
        if (retList == null || retList.Len() == 0) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        // 先判断这个要删除的元素是否存在，要是本来就不存在，那么什么也不用干
        String element = cmdLines[3];
        if (!retList.contains(element)) {
            return new String[][]{};
        }

        // 如果存在，那么回滚时再把它加进去，不过顺序可能和之前有点不同
        int count = Integer.parseInt(cmdLines[2]);
        String[] undos = new String[2+Math.abs(count)];
        for (int i=2;i<undos.length;i++) {
            undos[i] = cmdLines[3];
        }

        undos[0] = count > 0 ? "LPush" : "RPush";
        undos[1] = cmdLines[1];

        return new String[][]{
                undos
        };
    }

    /*
    //ltrim key start stop 包含 stop, 只保留 list 中从 start 到 stop 的元素
     */
    public static IReply execLTrim(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        if (retList == null || retList.Len() == 0) {
            return IntReply.makeIntReply(0);
        }

        try {
            int start = Integer.parseInt(cmdLines[2]);
            int stop = Integer.parseInt(cmdLines[3]);
            int size = retList.Len();
            if (start < -size) {
                start = 0;
            } else if (start < 0) {
                start += size;
            }

            if (stop < -size) {
                stop = 0;
            } else if (stop < 0) {
                stop += size;
            } else if (stop >= size) {
                stop = size - 1;
            }

            if (start > stop || start >= size) {
                return IntReply.makeIntReply(0);
            }

            // 移除 两头，保留 [start, stop]
            for (int i=0;i<start;i++) {
                retList.RemoveFirst();
            }

            for (int i=stop+1;i<size;i++) {
                retList.RemoveLast();
            }

            return OkReply.makeOKReply();
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    /*
    //  ltrim key start stop 包含 stop, 只保留 list 中从 start 到 stop 的元素
    */
    public static String[][] undoLTrim(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        // 在命令执行前，key 对应的 list 就不存在，那么回滚时仍然保持不存在
        if (retList == null || retList.Len() == 0) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        // 先判断 start 和 stop 确定的范围是否合法，如果不合法，那么什么也不用做
        try {
            int start = Integer.parseInt(cmdLines[2]);
            int stop = Integer.parseInt(cmdLines[3]);
            int size = retList.Len();
            if (start < -size) {
                start = 0;
            } else if (start < 0) {
                start += size;
            }

            if (stop < -size) {
                stop = 0;
            } else if (stop < 0) {
                stop += size;
            } else if (stop >= size) {
                stop = size - 1;
            }

            // 不合法
            if (start > stop || start >= size) {
                return new String[][]{};
            }

            // 合法，那就把 [0, start) 和 (stop, end] 都加进去
            // 先取出 [0, start) 范围内的元素
            ArrayList<String> firstPart = new ArrayList<>(), secondPart = new ArrayList<>();
            firstPart.add("LPush");
            firstPart.add(cmdLines[1]);

            secondPart.add("RPush");
            secondPart.add(cmdLines[1]);

            int finalStart = start;
            retList.forEach(false, new IListConsumer() {
                @Override
                public boolean consumer(int i, Object val) {
                    if (i < finalStart) {
                        firstPart.add((String) val);
                    } else {
                        return false;
                    }
                    return true;
                }
            });

            // 取出 (stop, end] 的元素
            int secondSize = size - stop - 1;
            retList.forEach(true, new IListConsumer() {
                @Override
                public boolean consumer(int i, Object val) {
                    if (i+1 <= secondSize) {
                        secondPart.add((String) val);
                    } else {
                        return false;
                    }
                    return true;
                }
            });

            return new String[][]{
                    firstPart.toArray(new String[0]),
                    secondPart.toArray(new String[0])
            };
        } catch (NumberFormatException e) {
            return new String[][]{};
        }
    }

//    LSET key index element
    public static IReply execLSet(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        if (retList == null || retList.Len() == 0) {
            return IntReply.makeIntReply(0);
        }

        try {
            int index = Integer.parseInt(cmdLines[2]);
            int size = retList.Len();
            if (index < -size || index >= size) {
                return StandardErrReply.makeErrReply("illegal index for LSet");
            } else if (index < 0) {
                index += size;
            }

            retList.set(index, cmdLines[3]);
            return OkReply.makeOKReply();
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    //    LSET key index element
    public static String[][] undoLSet(IDB idb, String[] cmdLines) {
        IJoList retList = getAsList(idb, cmdLines[1]);
        // 在命令执行前，key 对应的 list 就不存在，那么回滚时仍然保持不存在
        if (retList == null || retList.Len() == 0) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        int index = Integer.parseInt(cmdLines[2]);
        int size = retList.Len();
        // 检查 index 的合法性，如果不合法，那么回滚时什么也不用做
        if (index < -size || index >= size) {
            return new String[][]{};
        } else if (index < 0) {
            index += size;
        }

        Object oldObj = retList.get(index);
        // 设置成原来的值
        return new String[][] {
                {"LSet", cmdLines[1], Integer.toString(index), oldObj.toString()}
        };
    }
}
